package com.J.Plane;

import javax.microedition.khronos.opengles.GL10;

import junit.framework.Assert;

public class Frustum extends Entity {
	private static final float	DEFAULT_FOVY = Matrix44.DEG2RAD(45);
	private static final float 	DEFAULT_ASPECT = 4.0f/3.0f;
	
	private int mViewW;
	private int mViewH;
	
	private float mFovy;
	private float mAspect;
	
	private float mNear;
	private float mFar;
	private Matrix44 mCachedProjectionMatrix;
	
	private boolean mNeedUpdateProjection;
	
	public Frustum() {
		mViewW = 4;
		mViewH = 3;
		mFovy = DEFAULT_FOVY;
		mAspect = DEFAULT_ASPECT;
		mNear = 1;
		mFar = 1000;
		mCachedProjectionMatrix = new Matrix44();
		mNeedUpdateProjection = true;
	}
	
	public Frustum viewport(GL10 gl, int w, int h) {
		gl.glViewport(0, 0, w, h);
		
		mViewW = w;
		mViewH = h;
		mAspect = (float)w/(float)h;
		
		needUpdateProjection();
		return this;
	}
	

	public float aspect() {
		return mAspect;
	}
	public Frustum fovy(float angleDegree) {
		mFovy = Matrix44.DEG2RAD(angleDegree);
		needUpdateProjection();
		return this;
	}
	public float fovy() {
		return mFovy;
	}
	public Frustum near(float n) {
		mNear = n;
		needUpdateProjection();
		return this;
	}
	public float near() {
		return mNear;
	}
	public Frustum far(float f) {
		mFar = f;
		needUpdateProjection();
		return this;
	}
	public float far() {
		return mFar;
	}
	
	
	
	private void needUpdateProjection() {
		mNeedUpdateProjection = true;
	}
	
	/**
	 * Update projection matrix.
	 * Currently only perspective projection is implmented.
	 * Following codes are from Mesa3D/ Project.c
	 * 
	 */
	private void updateProjectionMatrix() {
		mNeedUpdateProjection = false;
		
		Assert.assertTrue(mFar>=mNear);
		
		float deltaZ = mFar - mNear;
		float radian = mFovy * 0.5f;
		float sin = (float)Math.sin((double)radian);
		
		Assert.assertTrue(deltaZ != 0.0f);
		Assert.assertTrue(sin != 0.0f);
		Assert.assertTrue(mAspect != 0.0f);
		
		float cotangent = (float)Math.cos((double)radian)/sin;
		
		Matrix44 m = mCachedProjectionMatrix;
		m.identity();
		m.n00 = cotangent / mAspect;
		m.n11 = cotangent;
		m.n22 = -(mFar + mNear) / deltaZ;
		m.n23 = -1;
		m.n32 = -2 * mNear * mFar / deltaZ;
		m.n33 = 0;		
	}
	
	/**
	 * Set projection matrix.
	 * @param [in]gl	OpenGL context
	 */
	public void setProjectionMatrix(GL10 gl) {
		if(mNeedUpdateProjection) {
			updateProjectionMatrix();
		}
		
		gl.glMatrixMode(GL10.GL_PROJECTION);
		gl.glLoadMatrixf(mCachedProjectionMatrix.getFloatBuffer());
	}
}


